package de.fhin.mas.agents.searcher.behaviours;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.SimpleBehaviour;
import jade.domain.FIPANames;
import jade.lang.acl.ACLMessage;
import jade.proto.AchieveREInitiator;

import java.util.Calendar;
import java.util.Date;
import java.util.Vector;
import java.util.logging.Level;

import de.fhin.mas.MASEvents;
import de.fhin.mas.agents.searcher.SearcherAgent;
import de.fhin.mas.util.MessageUtil;

/**
 * Dieses <tt>Behaviour</tt> ist dafür zuständig, jegliche anfallende
 * <tt>change_of_location_necessary</tt> - <tt>REQUEST</tt>s an den Navigator zu
 * senden und dabei den korrekten Ablauf des FIPA Request Protokolles zu
 * gewährleisten.
 * 
 * @author Alexander Weickmann
 */
public class ChangeOfLocationNecessaryRequestInitiator extends AchieveREInitiator {

	/** Die eindeutige ID zur Serialisierung ueber das Netzwerk. */
	private static final long serialVersionUID = -8188987124076691793L;

	/**
	 * Diese Zeit (Sekunden) haben die Empfänger des <tt>REQUEST</tt>s Zeit zu
	 * antworten ... nach Ablauf wird nach einer kurzen Wartezeit ein weiterer
	 * <tt>REQUEST</tt> versucht.
	 */
	private static final int TIMEOUT = 7;

	/**
	 * Diese Zeit wird bei einem missglückten <tt>REQUEST</tt> gewartet, bis ein
	 * neuer <tt>REQUEST</tt> versucht wird.
	 */
	private static final int RE_REQUEST_DELAY = 7000;

	private static final ACLMessage msg = MessageUtil.buildMessage(FIPANames.InteractionProtocol.FIPA_REQUEST,
			MASEvents.CHANGE_OF_LOCATION_NECESSARY.getName(), ACLMessage.REQUEST, null);

	/** Flag, welches anzeigt ob ein Request gestartet werden soll. */
	private boolean initRequest;

	private long lastRequest;

	/**
	 * Erzeugt einen <tt>AbortAndGotoRequestResponder</tt>.
	 * 
	 * @param a
	 *            Der Agent, zu dem das <tt>Behaviour</tt> gehört.
	 */
	public ChangeOfLocationNecessaryRequestInitiator(Agent a) {
		super(a, msg);
		registerPrepareRequests(new CheckRequest());

		this.initRequest = false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleFailure(ACLMessage failure) {
		reRequest();
		reset(msg);
	}

	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected void handleAllResponses(Vector responses) {
		if (responses.size() < 1) {
			reRequest();
			reset(msg);
		} else {
			ACLMessage response = (ACLMessage) responses.get(0);
			if (response.getPerformative() != ACLMessage.AGREE) {
				reRequest();
				reset(msg);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleInform(ACLMessage inform) {
		((SearcherAgent) myAgent).resumeTargetSearch();
		reset(msg);
	}

	/** Initiiert einen neuen <tt>REQUEST</tt>. */
	private void reRequest() {
		// Neuer Request soll initiiert werden
		initRequest();
	}

	/** Löst einen Request aus. */
	public void initRequest() {
		initRequest = true;
	}

	/**
	 * Mit diesem State wird überprüft, ob ein Request initiiert werden muss,
	 * gegebenenfalls wird dies dann getan.
	 */
	private class CheckRequest extends SimpleBehaviour {

		/** Die eindeutige ID zur Serialisierung ueber das Netzwerk. */
		private static final long serialVersionUID = 6948778071624780857L;

		/** Flag, das einzeigt ob der State beendet wurde. */
		private boolean finished;

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void action() {
			if (initRequest && lastRequest < System.currentTimeMillis() - RE_REQUEST_DELAY) {

				ACLMessage request = (ACLMessage) getDataStore().get(REQUEST_KEY);
				SearcherAgent searcherAgent = (SearcherAgent) myAgent;
				Vector<ACLMessage> retVector = new Vector<ACLMessage>();

				// TODO: Wenn ihr nur mit Millisekunden arbeitet ist Calendar
				// übertrieben. Date genügt hier.
				Calendar calendar = Calendar.getInstance();
				calendar.set(Calendar.SECOND, calendar.get(Calendar.SECOND) + TIMEOUT);
				Date replyByDate = calendar.getTime();

				// An Navigator schicken falls vorhanden
				AID navigator = searcherAgent.getNavigator();
				if (navigator == null) {
					searcherAgent.getLogger().log(Level.WARNING, "No navigator agent available.");
				} else {
					ACLMessage navigatorMsg = (ACLMessage) request.clone();
					navigatorMsg.addReceiver(navigator);
					navigatorMsg.setReplyByDate(replyByDate);
					retVector.add(navigatorMsg);
				}

				getDataStore().put(ALL_REQUESTS_KEY, retVector);

				/*
				 * Wir merken uns wann wir den Request gesendet haben, damit wir
				 * eventuelle weitere Versuche etwas verzögern können
				 */
				lastRequest = System.currentTimeMillis();

				finished = true;
			}

			block();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public int onEnd() {
			// Neues initiieren für später wieder möglich machen
			initRequest = false;

			finished = false;
			return super.onEnd();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean done() {
			return finished;
		}

	}

}
